﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace BlackStar
{
    public enum MouseButton : int
    {
        Left = 0,
        Right = 1,
        Middle = 2,
        X1 = 3,
        X2 = 4
    }

    public class MouseEventArgs
    {
        private Point myLocation;
        public Point Location { get { return myLocation; } }

        private MouseButtons myButtons;
        public MouseButtons Buttons { get { return myButtons; } }

        private int myScrollChange = 0;
        public int ScrollChange { get { return myScrollChange; } }

        public class MouseButtons
        {
            private bool[] myButtons;
            internal MouseButtons(bool[] buttons) { myButtons = buttons; }
            public bool Left { get { return myButtons[(int)MouseButton.Left]; } }
            public bool Right { get { return myButtons[(int)MouseButton.Right]; } }
            public bool Middle { get { return myButtons[(int)MouseButton.Middle]; } }
            public bool X1 { get { return myButtons[(int)MouseButton.X1]; } }
            public bool X2 { get { return myButtons[(int)MouseButton.X2]; } }
            public bool this[MouseButton b] { get { return myButtons[(int)b]; } }
        }

        internal MouseEventArgs(MouseButtons b, Point p, int scrollChange)
        {
            this.myLocation = p;
            this.myButtons = b;
            this.myScrollChange = scrollChange;
        }
    }

    public delegate void MouseEvent(MouseEventArgs e);

    public class Mouse
    {
        public event MouseEvent MouseClick;
        public event MouseEvent MouseDoubleClick;
        public event MouseEvent MouseDown;
        public event MouseEvent MouseUp;
        public event MouseEvent MouseMove;
        public event MouseEvent MouseScroll;

        private int[] Up1 = { 0, 0, 0, 0, 0 };
        private int[] Up2 = { 0, 0, 0, 0, 0 };
        private int[] Dn1 = { 0, 0, 0, 0, 0 };
        private int[] Dn2 = { 0, 0, 0, 0, 0 };

        private bool[] singleFired = { false, false, false, false, false };

        private bool IsSingleClick(MouseButton b)
        { return (Up1[(int)b] > 0) && (Dn1[(int)b] > 0); }

        private bool IsDoubleClick(MouseButton b)
        { return IsSingleClick(b) && (Up2[(int)b] > 0) && (Dn2[(int)b] > 0); }

        private void ButtonDown(MouseButton b)
        {
            OnMouseDown();
            if (Dn1[(int)b] > 0) { Dn2[(int)b] = Dn1[(int)b]; }
            Dn1[(int)b] = 1000;
        }

        private void ButtonUp(MouseButton b)
        {
            OnMouseUp();
            if (Up1[(int)b] > 0) { Up2[(int)b] = Up1[(int)b]; }
            Up1[(int)b] = 1000;
            if (!singleFired[(int)b] && IsSingleClick(b))
            {
                OnMouseClick();
                singleFired[(int)b] = true;
            }
            if (IsDoubleClick(b))
            {
                OnMouseDoubleClick();
                singleFired[(int)b] = false;
                Up1[(int)b] = 0; Up2[(int)b] = 0; Dn1[(int)b] = 0; Dn2[(int)b] = 0;
            }
        }

        private bool Pressed(MouseState state, MouseButton b)
        {
            switch (b)
            {
                case MouseButton.Left:
                    return state.LeftButton == ButtonState.Pressed;
                case MouseButton.Right:
                    return state.RightButton == ButtonState.Pressed;
                case MouseButton.Middle:
                    return state.MiddleButton == ButtonState.Pressed;
                case MouseButton.X1:
                    return state.XButton1 == ButtonState.Pressed;
                case MouseButton.X2:
                    return state.XButton2 == ButtonState.Pressed;
                default:
                    return false;
            }
        }

        public MouseEventArgs getArgs()
        {
            float currentScroll = (float)currentState.ScrollWheelValue / 120;
            float lastScroll = (float)lastState.ScrollWheelValue / 120;
            MouseEventArgs result = new MouseEventArgs(
                new MouseEventArgs.MouseButtons(getPressed()), this.Location,
                (int)Math.Truncate(lastScroll - currentScroll));

            return result;
        }

        private bool[] getPressed()
        {
            int count = Enum.GetValues(typeof(MouseButton)).Length;
            MouseButton[] values = (MouseButton[])Enum.GetValues(typeof(MouseButton));
            bool[] result = new bool[count];
            for (int i = 0; i < count; i++) { result[i] = Pressed(currentState, values[i]); }
            return result;
        }

        private void DoButtonChecks()
        {
            foreach (MouseButton b in Enum.GetValues(typeof(MouseButton)))
            {
                if (Pressed(currentState, b) && !Pressed(lastState, b)) ButtonDown(b);
                if (!Pressed(currentState, b) && Pressed(lastState, b)) ButtonUp(b);
            }
        }

        private MouseState lastState;
        private MouseState currentState;

        GUI gui;

        string selected = "Default";

        public bool Visible = true;

        public Color Color = Color.White;

        public Mouse(GUI g)
        {
            gui = g;

            // Setup Mouse States.
            lastState = currentState = Microsoft.Xna.Framework.Input.Mouse.GetState();
        }

        public string Cursor
        {
            get { return selected; }
            set { selected = value; }
        }

        public Point Location
        {
            get { return new Point(currentState.X, currentState.Y); }
            set
            {
                Microsoft.Xna.Framework.Input.Mouse.SetPosition(value.X, value.Y);
                UpdateState();
            }
        }

        private void UpdateState()
        {
            lastState = currentState;
            currentState = Microsoft.Xna.Framework.Input.Mouse.GetState();
        }

        public void Update(GameTime gameTime)
        {
            UpdateState();
            int time = gameTime.ElapsedGameTime.Milliseconds;
            foreach (MouseButton b in Enum.GetValues(typeof(MouseButton)))
            {
                Up1[(int)b] = Math.Max(0, Up1[(int)b] - time);
                Up2[(int)b] = Math.Max(0, Up2[(int)b] - time);
                Dn1[(int)b] = Math.Max(0, Dn1[(int)b] - time);
                Dn2[(int)b] = Math.Max(0, Dn2[(int)b] - time);
                singleFired[(int)b] = (Up1[(int)b] > 0) && (Dn1[(int)b] > 0);
            }

            DoButtonChecks();

            if (currentState.ScrollWheelValue != lastState.ScrollWheelValue)
            {
                OnMouseScroll();
            }

        }

        public bool MouseInViewport()
        {
            Rectangle viewport = new Rectangle(0, 0, gui.GraphicsDevice.Viewport.Width,
                gui.GraphicsDevice.Viewport.Height);
            return viewport.Contains(this.Location);
        }

        protected void OnMouseMove()
        { if ((MouseMove != null) && MouseInViewport()) MouseMove(getArgs()); }

        protected void OnMouseDown()
        { if ((MouseDown != null) && MouseInViewport()) MouseDown(getArgs()); }

        protected void OnMouseUp()
        { if ((MouseUp != null) && MouseInViewport()) MouseUp(getArgs()); }

        protected void OnMouseClick()
        { if ((MouseClick != null) && MouseInViewport()) MouseClick(getArgs()); }

        protected void OnMouseDoubleClick()
        { if ((MouseDoubleClick != null) && MouseInViewport()) MouseDoubleClick(getArgs()); }

        protected void OnMouseScroll()
        { if ((MouseScroll != null) && MouseInViewport()) MouseScroll(getArgs()); }

        public void Draw()
        {
            if (!Visible) return;
            if (currentState.X >= 0 && currentState.Y >= 0)
            {
                Component c = gui.FindComponent(this.Location);
                string strCursor = (c != null) ? c.Cursor : "Default";

                gui.theme.Cursors[strCursor].Draw(gui.batch, this.Location, this.Color);
            }
        }

        public bool LeftButton { get { return Pressed(currentState, MouseButton.Left); } }
        public bool RightButton { get { return Pressed(currentState, MouseButton.Right); } }
        public bool MiddleButton { get { return Pressed(currentState, MouseButton.Middle); } }
        public bool X1Button { get { return Pressed(currentState, MouseButton.X1); } }
        public bool X2Button { get { return Pressed(currentState, MouseButton.X2); } }
    }
}
